/**
 * OSAmI: Open Source Ambient Intelligence
 * Servlets running on the gateway, for displaying data from the sensor nodes.
 * Copyright (C) Jonathan Ferreira 2010 (internship in LCIS-INPG, for a demo in the FR part of the OSAMI project)
 * Contact: osami-general@osami-commons.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
 */
package org.osami.gateway.display.internal.rule;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.felix.ipojo.annotations.Bind;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Instantiate;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.Unbind;
import org.apache.felix.ipojo.annotations.Validate;
import org.osami.gateway.display.internal.ServletPaths;
import org.osami.gateway.display.internal.uri.UriAnalyzer;
import org.osami.gateway.display.internal.uri.internal.DefaultUriAnalyzer;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.ow2.jasmine.rules.api.IDroolsWorkingMemory;
import org.ow2.jasmine.rules.api.IDroolsWorkingMemoryPool;
import org.ow2.jasmine.rules.osgi.api.IPackageAdder;

@Component
@Instantiate
public class RuleControllerServlet extends HttpServlet {

    public static final String DEFAULT_WORKING_MEMORY_NAME = "default";

    private static final long serialVersionUID = 1L;

    @Requires
    private HttpContext context;

    @Requires
    private HttpService httpService;

    /**
     * Active services.
     */
    private List<ServiceReference> references = new ArrayList<ServiceReference>();

    /**
     * Inactive services (were active but not ATM).
     */
    private List<Bundle> bundles = new ArrayList<Bundle>();

    private UriAnalyzer startPattern = new DefaultUriAnalyzer("/{bundle-id}/start");

    private UriAnalyzer stopPattern = new DefaultUriAnalyzer("/{bundle-id}/stop");

    @Requires
    private IDroolsWorkingMemoryPool pool;

    @Validate
    public void start() throws ServletException, NamespaceException {
        this.httpService.registerServlet(ServletPaths.RULE, this, null, this.context);
    }

    @Invalidate
    public void stop() {
        this.httpService.unregister(ServletPaths.RULE);
    }

    @Bind(aggregate = true, optional = true)
    public void bindPackageAdder(IPackageAdder adder, ServiceReference reference) {
        this.references.add(reference);
        this.bundles.remove(reference.getBundle());
    }

    @Unbind
    public void unbindPackageAdder(ServiceReference reference) {
        this.references.remove(reference);
        Bundle bundle = reference.getBundle();
        if (!this.bundles.contains(bundle)) {
            this.bundles.add(bundle);
        }
    }

    @Override
    synchronized public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        PrintWriter writer = response.getWriter();

        writer.println("<?xml version='1.0'?>");
        writer.println("<html xmlns='http://www.w3.org/1999/xhtml' lang='en'>");

        // HTML Head
        // ---------------------------------------------------
        writer.println("<head>");
        writer.println("  <title>Activate/Deactivate Rules</title>");
        writer.println("  <link href='/resource/css/designSensor.css' rel='stylesheet' type='text/css' />");
        writer.println("</head>");

        // HTML Body
        // ---------------------------------------------------
        writer.println("<body>");

        // Start / Stop the Bundle
        // ---------------------------------------------------
        try {
            Map<String, String> startValues = this.startPattern.analyze(request.getPathInfo());
            if (startValues != null) {
                // Start a bundle
                Long id = Long.valueOf(startValues.get("bundle-id"));
                startBundle(id);

            } else {
                Map<String, String> stopValues = this.stopPattern.analyze(request.getPathInfo());
                if (stopValues != null) {
                    // Stop a Bundle
                    Long id = Long.valueOf(stopValues.get("bundle-id"));
                    stopBundle(id);
                }
            }
        } catch (Exception be) {
            printException(writer, be);
        }


        // Print title
        // ---------------------------------------------------
        writer.println("<div style='font-color:grey;text-align:center'>");
        writer.println("  <h1>Rules Services</h1>");
        writer.println("</div>");


        printRules(writer);

        // Horizontal rule
        writer.println("<hr/>");
        writer.println("<a href='/'>Back to index ...</a>");

        writer.println("</body>");
        writer.println("</html>");
    }

    private void printException(PrintWriter writer, Exception be) {
        writer.println("<table style='width:90%;height:100px;cellpadding:0;background-color:grey'>");

        Throwable throwable = be;
        do {
            printExceptionMessage(writer, throwable.getMessage());
            for (StackTraceElement element : throwable.getStackTrace()) {
                printExceptionLine(writer, element);
            }
            writer.println("<br />");

            throwable = throwable.getCause();
        } while (throwable != null);

        writer.println("</table>");
    }

    private void printExceptionLine(PrintWriter writer, StackTraceElement element) {
        writer.println("<tr>");
        writer.println("  <td style='text-style:italic'>");
        writer.println("&nbsp;&nbsp;&nbsp;");
        writer.print(element.getClassName() + "." + element.getMethodName());
        writer.println("(" + element.getFileName() + ":" + element.getLineNumber() + ")");
        writer.println("  </td>");
        writer.println("</tr>");
    }

    private void printExceptionMessage(PrintWriter writer, String message) {
        writer.println("<tr>");
        writer.println("  <td style='text-style:bold'>");
        writer.println(message);
        writer.println("  </td>");
        writer.println("</tr>");
    }

    private void printRules(PrintWriter writer) {

        // start table
        writer.println("<table width='100%' cellpadding='0'>");

        // headers
        writer.println("<tr>");
        writer.println("<th width='10%'>ID</th>");
        writer.println("<th width='70%'>Name</th>");
        writer.println("<th>Operations</th>");
        writer.println("</tr>");

        // body

        // Print activate rules
        for (ServiceReference reference : this.references) {
            printRuleInfo(writer, reference);
        }

        // Print non-activated rules
        for (Bundle bundle : this.bundles) {
            printRuleInfo(writer, bundle);
        }

        // end
        writer.println("</table>");
    }

    private void startBundle(Long bundleId) throws Exception {

        // FIXME: We need to explicitly stops the WM before adding the new rules
        // Otherwise, we can fall on some synchronization issues
        IDroolsWorkingMemory workingMemory = this.pool.get(DEFAULT_WORKING_MEMORY_NAME);
        try {
            workingMemory.stop();

            Bundle bundle = findBundle(bundleId);
            if (bundle != null) {
                bundle.start();
            }
        } finally {
            // Restart the WM
            workingMemory.start();
        }
    }

    private void stopBundle(Long bundleId) throws BundleException {
        Bundle bundle = findBundle(bundleId);
        if (bundle != null) {
            bundle.stop();
        }
    }

    private Bundle findBundle(Long bundleId) {
        // Look in active references first
        for (ServiceReference reference : this.references) {
            if (bundleId.equals(reference.getBundle().getBundleId())) {
                return reference.getBundle();
            }
        }
        // Then in installed Bundle
        for (Bundle bundle : this.bundles) {
            if (bundleId.equals(bundle.getBundleId())) {
                return bundle;
            }
        }
        // nothing found
        return null;
    }

    private void printRuleInfo(final PrintWriter writer,
                               final ServiceReference reference) {

        writer.println("<tr>");

        Bundle bundle = reference.getBundle();

        // ID
        String id = bundle.getBundleId() + "/" + reference.getProperty(Constants.SERVICE_ID);
        writer.println("<td>");
        writer.println(id);
        writer.println("</td>");

        // Bundle Name
        writer.println("<td>");
        writer.println(bundle.getSymbolicName() + " [" + bundle.getVersion() + "]");
        writer.println("</td>");

        // Operations form
        writer.println("<td>");
        printOperationForm(writer, bundle, true);
        writer.println("</td>");

        writer.println("</tr>");
    }

    private void printRuleInfo(PrintWriter writer, Bundle bundle) {

        writer.println("<tr>");

        // ID
        writer.println("<td>");
        writer.println(bundle.getBundleId());
        writer.println("</td>");

        // Bundle Name
        writer.println("<td>");
        writer.println(bundle.getSymbolicName() + " [" + bundle.getVersion() + "]");
        writer.println("</td>");

        // Operations form
        writer.println("<td>");
        printOperationForm(writer, bundle, false);
        writer.println("</td>");

        writer.println("</tr>");
    }

    private void printOperationForm(final PrintWriter writer,
                                    final Bundle bundle,
                                    final boolean started) {

        String startDisabled = "";
        String stopDisabled = "disabled='disabled'";
        if (started) {
            startDisabled = "disabled='enabled'";
            stopDisabled = "";
        }

        String baseUrl = ServletPaths.RULE + "/" + bundle.getBundleId();
        String startUrl = baseUrl + "/start";
        writer.println("<form method='get' action='" + startUrl + "'>");
        writer.println("  <input type='submit' value='Start' " + startDisabled + " />");
        writer.println("</form>");

        String stopUrl = baseUrl + "/stop";
        writer.println("<form method='get' action='" + stopUrl + "'>");
        writer.println("  <input type='submit' value='Stop' " + stopDisabled + " />");
        writer.println("</form>");
    }

}
